/*
 * Copyright (C) 2019 C-SKY Microsystems Co., Ltd. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/******************************************************************************
 * @file     test_vpu.c
 * @brief    Encode test case
 * @version  V1.6
 * @date     27. May 2019
 ******************************************************************************/

#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <stdbool.h>
#include <stdio.h>
#include <aos/aos.h>
#include <aos/debug.h>

#define LOG_LEVEL 0
#include <syslog.h>
#include <ulog/ulog.h>

//#include <dtest.h>
// #include <test_driver_config.h>

#include <soc.h>
#include <drv_vpu.h>
// #include "test_vpu_internal.h"

#include "vpu_internal.h"

#include "mm_config.h"

#include "h264_vpu.h"

#define PRINT_STREAM_OUTPUT 1

#define TAG "vpu"

#if !defined(MAX)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif

#define HW_PRINTF  printf
#define ASSERT_TRUE(expr, value) \
    do { \
        bool result = (expr); \
        if (!result) { \
            printf("%s  actual error line %d,value =%d\n", __FILE__, __LINE__,value);\
            return ;\
        }\
    } while (0)

#define ASSERT_TRUE_WITH_RETURN(expr, value, ret) \
    do { \
        bool result = (expr); \
        if (!result) { \
            printf("%s  actual error line %d,value =%d\n", __FILE__, __LINE__,value);\
            return ret;\
        }\
    } while (0)

#define CHECK_VPU_RETURN(expr, format, args...)  \
			do {											  \
				if (!(expr)) {								  \
					printf("error: "); \
					printf(format,##args);\
					return -1; 								  \
				}											  \
			} while(0)

// static const char* const encode_frame_type_str[] = {
//     "I-Frame", "P-Frame", "Not-Frame"
// };

static vpu_handle_t handle_dev;
static uint32_t event_count[VPU_EVENT_ENCODE_ERROR + 1];

#define H264_ES_HEADER_LEN 4
// static const char h264_es_header[H264_ES_HEADER_LEN] = {0x00, 0x00, 0x00, 0x01};

static volatile int frame_encode_ready = 0;
vpu_h264enc_config_t config;
vpu_instance_t handle_enc;

/* Start decoder and get stream header */
vpu_picture_t input;
vpu_es_frame_t output;

//#define INPUT_IMAGE_ADDR 0x6600000
//#define INPUT_IMAGE_ADDR 0x00d38400
#define INPUT_IMAGE_ADDR0 MM_IMAGE_DPU_BUF0//0x01070800
#define INPUT_IMAGE_ADDR1 MM_IMAGE_DPU_BUF1//0x01070800
#define INPUT_IMAGE_ADDR2 MM_IMAGE_DPU_BUF2//0x01070800

#define output_en 0
static volatile uint32_t h264_header_en = 1;


typedef struct {
    uint8_t   *vpu_data_start_addr;   ///< input data address info
    uint32_t  vpu_data_length;   ///< encode frame type
} vpu_frame_result;

typedef struct {
    uint8_t   *output_addr;   
    uint32_t   output_length; 
} vpu_output_frame;



vpu_output_frame g_vpu_output_info;

uint8_t *output_stream_addr = NULL;


uint32_t stats_stream_length = 0;
uint32_t input_frame_count = 0;

uint32_t input_frame_size = 0;

uint32_t feeded_frame_count = 0;  /* The count that feeded to encoder */
uint32_t encoded_frame_count = 0; /* The count that encoded successfully */
uint32_t next_pic = 0; 

extern int volatile ispx_buf_index;

typedef struct {
	uint8_t *addr;
	int	size;
	int cap;			// capability: total size
	h264_frame_type_e frame_type;
	long long time_stamp;
} h264_frame_t;

typedef struct {
	h264_frame_t *cache;
	size_t	cache_size;
	int	rd;
	int wt;
	aos_mutex_t lock;
} h264_frame_ringbuff_t;

static h264_frame_ringbuff_t * h264_cache_init(size_t frame_num)
{
	h264_frame_ringbuff_t *ringbuff;
	int ret;

	ringbuff = (h264_frame_ringbuff_t *)aos_zalloc_check(sizeof(h264_frame_ringbuff_t));
	ringbuff->cache = (h264_frame_t *)aos_zalloc_check(sizeof(h264_frame_t) * (frame_num + 1));
	ringbuff->cache_size = frame_num + 1;

	ret = aos_mutex_new(&ringbuff->lock);
	CHECK_RET_WITH_RET(ret == 0, NULL);

	return ringbuff;
}
#if 0
static int h264_cache_put(h264_frame_ringbuff_t *hdl, h264_frame_type_e type, uint8_t *frame, int size, long long time_stamp)
{
	aos_check_param(hdl && frame && size > 0);
	
	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;
	static int log_ctrl = 0;

	if ((wt + 1) % hdl->cache_size == rd) {
		if (++log_ctrl % 100 == 0) {
			LOGE(TAG, "cahce full");
			log_ctrl = 0;
		}

		hdl->rd = (rd + 1) % hdl->cache_size;	// move rd pointer to drop oldest frame
	}

	if (hdl->cache[wt].addr == NULL || hdl->cache[wt].cap < size)
	{
		hdl->cache[wt].addr = (uint8_t *)aos_realloc_check(hdl->cache[wt].addr, size);
		hdl->cache[wt].cap = size;
	}

	memcpy(hdl->cache[wt].addr, frame, size);
	hdl->cache[wt].size = size;
	hdl->cache[wt].frame_type = type;
	hdl->cache[wt].time_stamp = time_stamp;

	hdl->wt = (wt + 1) % hdl->cache_size;	// move wt pointer to insert new frame

	aos_mutex_unlock(&hdl->lock);

	return 0;
}
#endif
static int h264_cache_put_async(h264_frame_ringbuff_t *hdl, h264_frame_type_e type, uint8_t *frame, int size, long long time_stamp)
{
	aos_check_param(hdl && frame && size > 0);

	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;
	// static int log_ctrl = 0;
	
	if ((wt + 1) % hdl->cache_size == rd) {
		// if (++log_ctrl % 100 == 0) {
		// 	LOGE(TAG, "cahce full");
		// 	log_ctrl = 0;
		// }
		LOGE(TAG, "cahce full");
		/* drop till next I-frame */
		hdl->cache[rd].size = 0;
		rd = (rd + 1) % hdl->cache_size;	// move rd pointer to drop oldest frame

		while (wt != rd && hdl->cache[rd].frame_type != H264_TYPE_IFRAME) {
			hdl->cache[rd].size = 0;				// free this unit
			rd = (rd + 1) % hdl->cache_size;
		}

		hdl->rd = rd;
	}

	if (hdl->cache[wt].addr == NULL || hdl->cache[wt].size + size > hdl->cache[wt].cap)
	{
		hdl->cache[wt].addr = (uint8_t *)aos_realloc_check(hdl->cache[wt].addr, hdl->cache[wt].size + size);
		hdl->cache[wt].cap = hdl->cache[wt].size + size;
	}

	memcpy(hdl->cache[wt].addr + hdl->cache[wt].size, frame, size);
	hdl->cache[wt].size = hdl->cache[wt].size + size;
	hdl->cache[wt].time_stamp = time_stamp;

	if (type >= 0)
		hdl->cache[wt].frame_type = type;

	aos_mutex_unlock(&hdl->lock);

	return 0;
}

static int h264_cache_put_done(h264_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);

	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;

	if ((wt + 1) % hdl->cache_size == rd) {
		LOGE(TAG, "no write");
		aos_mutex_unlock(&hdl->lock);
		return -1;
	}
	
	hdl->wt = (wt + 1) % hdl->cache_size;

	aos_mutex_unlock(&hdl->lock);

	return 0;
}

static int h264_cache_get(h264_frame_ringbuff_t *hdl, h264_frame_type_e *type, uint8_t *frame, int max_size, long long *time_stamp)
{
	aos_check_param(hdl);
	
	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;
	int get_size = 0;

	if (wt == rd) {
		aos_mutex_unlock(&hdl->lock);
		return -1;
	}

	if (frame && max_size < hdl->cache[rd].size) {
		LOGE(TAG, "frame over size size=%d in_size=%d", hdl->cache[rd].size, max_size);
		aos_mutex_unlock(&hdl->lock);
		return -1;
	}

	if (frame) {
		memcpy(frame, hdl->cache[rd].addr, hdl->cache[rd].size);
		get_size = hdl->cache[rd].size;
	}

	if (type) {
		*type = hdl->cache[rd].frame_type;
	}

	if (time_stamp) {
		*time_stamp = hdl->cache[rd].time_stamp;
	}

	hdl->cache[rd].size = 0;				// free this unit
	hdl->rd = (rd + 1) % hdl->cache_size;

	aos_mutex_unlock(&hdl->lock);

	return get_size;
}

static int h264_cache_seek_iframe(h264_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);
	
	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

	int wt = hdl->wt;
	int rd = hdl->rd;

	while (wt != rd && hdl->cache[rd].frame_type != H264_TYPE_IFRAME) {
		hdl->cache[rd].size = 0;				// free this unit
		rd = (rd + 1) % hdl->cache_size;
	}

	hdl->rd = rd;
	aos_mutex_unlock(&hdl->lock);

	return wt != rd ? 0 : -1;
}

// static void h264_cache_get_done(h264_frame_ringbuff_t *hdl)
// {
// 	aos_check_param(hdl);

// 	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);

// 	int wt = hdl->wt;
// 	int rd = hdl->rd;

// 	if (wt != rd) {
// 		hdl->cache[rd].size = 0;
// 		hdl->rd = (rd + 1) % hdl->cache_size;
// 	}

// 	aos_mutex_unlock(&hdl->lock);
// }

static int h264_cache_available_read(h264_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);

	int wt = hdl->wt;
	int rd = hdl->rd;
	size_t size = hdl->cache_size;

	return wt >= rd ? wt - rd : wt + (size - rd);
}

static int h264_cache_reset(h264_frame_ringbuff_t *hdl)
{
	aos_check_param(hdl);
	
	aos_mutex_lock(&hdl->lock, AOS_WAIT_FOREVER);
	for(int i=0; i < hdl->cache_size; i++) {
		hdl->cache[i].size = 0;
		hdl->cache[i].cap = 0;
	}
	hdl->wt = hdl->rd = 0;
	aos_mutex_unlock(&hdl->lock);
	return 0;
}

static void vpu_event_cb(vpu_instance_t vpu_inst, uint32_t event, void* user_data)
{
    if (event == VPU_EVENT_ENCODE_DONE) {
        frame_encode_ready = 1;
    } else if (event == VPU_EVENT_ENCODE_ERROR) {
        LOG_E("encode error\n");
		frame_encode_ready = 1;
    } else {
        LOG_E("Event=%d not support\n", event);
        return;
    }
    event_count[event]++;
}
#if 0
static void vpu_event_flush(void)
{
    memset(event_count, 0, sizeof(event_count));
}
#endif
void vpu_h264_init(void)
{
    ENTER();
	drv_vpu_clk_enable(ENABLE);
    handle_dev = csi_vpu_initialize(0, vpu_event_cb, NULL);

    EXIT_VOID();
}
#if 0
static void vpu_h264_setup(void)
{
    ENTER();
    vpu_event_flush();

    EXIT_VOID();
}

static void vpu_h264_cleanup(void)
{
    ENTER();

    EXIT_VOID();
}
static void vpu_h264_teardown(void)
{
    ENTER();
    csi_vpu_uninitialize(handle_dev);
    EXIT_VOID();
}
#endif
static int32_t vpu_h264enc_init_config(vpu_h264enc_config_t *config,
                                            uint32_t *frame_count)
{
    /* The yuv_file_frames should less than YUV file frame count */
	if (frame_count)
		*frame_count = 25; //2 second
#if 0
    /* Encoder input parameters */
    config->input_width = 320;
    config->input_height = 240;
    config->format =VPU_INPUT_FORMAT_YUV420_PLANAR ;//VPU_INPUT_FORMAT_YUV420_PLANAR;//VPU_INPUT_FORMAT_RGB888;//VPU_INPUT_FORMAT_BGR888;
    /* Encoder output parameters */
    config->output_width = config->input_width;
    config->output_height = config->input_height;
    config->level = VPU_H264ENC_LEVEL_3;
    config->framerate_numer = 25; //5 frames per second
    config->framerate_denom = 1; /* Can't be 0 */
    config->gop_len = 5;
#else
	/* Encoder input parameters */
	config->input_width = 720;//360;//720;//480;
	config->input_height = 1280;//640;//1280;//640;
	config->format =VPU_INPUT_FORMAT_RGB888 ;//VPU_INPUT_FORMAT_YUV420_PLANAR;//VPU_INPUT_FORMAT_RGB888;//VPU_INPUT_FORMAT_BGR888;//VPU_INPUT_FORMAT_YUYV422
	/* Encoder output parameters */
	config->output_width = config->input_width;
	config->output_height = config->input_height;
	config->level = VPU_H264ENC_LEVEL_5;
	config->framerate_numer = 25; //5 frames per second
	config->framerate_denom = 1; /* Can't be 0 */
	config->gop_len = 25;
#endif
    /* Check parameters */
    if (config->format > VPU_INPUT_FORMAT_BGR101010 ||
        config->framerate_denom == 0 ||
        (config->framerate_numer / config->framerate_denom) < 1) {
        LOG_E("Parameter error\n");
        return -1;
    }

    LOG_I("H264 encode config parameters are:\n"
          "    input  :%dx%d, format=%d,\n"
          "    output :%dx%d, level=%d,\n"
          "    framerate=%d/%d, gop=%d\n",
          config->input_width, config->input_height, config->format,
          config->output_width, config->output_height, config->level,
          config->framerate_numer, config->framerate_denom, config->gop_len);
    return 0;
}

           /* The next frame index to be encode */

void vpu_h264enc_config(void)
{
    ENTER();
    int32_t ret;
    /**** Prepare Encoder ****************************************************/
    ret = vpu_h264enc_init_config(&config, &input_frame_count);
    ASSERT_TRUE(ret == 0,ret);
	
    EXIT_VOID();
}

int vpu_frame_encode_start()
{
	int ret = 0;

	/* Create H264 encoder instance */
	handle_enc = csi_vpu_create_instance(handle_dev,
									 VPU_INSTANCE_TYPE_ENCODER_H264,
									 (vpu_inst_config_t)&config);
	CHECK_VPU_RETURN((handle_enc != NULL), "vpu_frame_encode_start() failed\n");
#if output_en    
	if(NULL == output_stream_addr){
		output_stream_addr =(uint8_t *)malloc(config.input_width *config.input_height *5 );
    }
	CHECK_VPU_RETURN((output_stream_addr != NULL),"vpu_frame_encode_start() failed\n");
	memset((void *)output_stream_addr,0x0,config.input_width *config.input_height *5 );
#endif

    return ret;
}

int vpu_frame_encode_h264_header(vpu_frame_result *frame_data)
{
    int ret = 0;
	/**** Start Encoder h264_header**********************************/
	ret = csi_vpu_h264enc_start(handle_enc, &output);
	ASSERT_TRUE_WITH_RETURN((ret == 0), ret, ret);
#if 0
	uint8_t *test;
    test = output.addr;
    for(uint32_t i = 0;i < output.size;i++){
    printf("output_addr= 0x%08x,value= 0x%02x\r\n",test,*test);
	test++;
    }
	test = NULL;
#endif
	frame_data->vpu_data_start_addr = output.addr;
	frame_data->vpu_data_length = output.size;
#if (PRINT_STREAM_OUTPUT == 1)
		printf("H264_header: header_out_addr=%p, size=%d\n",output.addr,output.size);
#endif

#if output_en    
		memcpy((void*)(output_stream_addr + stats_stream_length),output.addr, output.size);
		stats_stream_length += output.size;
#endif
    return ret;
}


int vpu_frame_encode_begin(void)
{
	int ret = 0;
	uint32_t wait_count = 0;
	uint8_t *input_raw_addrs[3] = {(uint8_t*)INPUT_IMAGE_ADDR0, (uint8_t*)INPUT_IMAGE_ADDR1, (uint8_t*)INPUT_IMAGE_ADDR2};

	if(next_pic == config.gop_len)
	{
	   next_pic = 0;
	}

	if (next_pic % config.gop_len == 0)
	{
		input.frame_type = VPU_FRAME_TYPE_IFRAME;
	}
	else
	{
		input.frame_type = VPU_FRAME_TYPE_PFRAME;
	}

	input.input_addr = input_raw_addrs[ispx_buf_index % 3];
	ret = csi_vpu_h264enc_encode(handle_enc, &input);
	ASSERT_TRUE_WITH_RETURN(ret == 0, ret, ret);

	/* wait for interrupt */
	while (frame_encode_ready == 0)
	{
		wait_count ++;
	//	mdelay(1);
		aos_msleep(1);
		if(1000 == wait_count){
			printf("H264_encode_wait timeout: encode_fail\n");
			break;
		}
	}
	frame_encode_ready = 0;


	
	return ret;
}



int vpu_frame_encode_data(vpu_frame_result *frame_data)
{
	int ret = 0;
	
	vpu_picture_t *input_picture;
	ret = csi_vpu_h264enc_get_stream(handle_enc, &input_picture, &output);
	ASSERT_TRUE_WITH_RETURN(ret == 0, ret, ret);
#if (PRINT_STREAM_OUTPUT == 1)
	// printf("Encode: index=%d, out_addr=%p, size=%d, type=%s\n",
	//                 next_pic,output.addr,output.size,encode_frame_type_str[input.frame_type]);
#endif
 #if output_en  
	memcpy((void *)(output_stream_addr + stats_stream_length),output.addr, output.size);
	stats_stream_length += output.size;
#endif
#if 0
	uint8_t *test;
    test = output.addr;
    for(uint32_t i = 0;i < 10;i++){
    printf("output_addr= 0x%08x,value= 0x%02x\r\n",test,*test);
	test++;
    }
	test = NULL;
#endif
	frame_data->vpu_data_start_addr = output.addr;
	frame_data->vpu_data_length = output.size;
	next_pic++;


	
	return ret;
}



int vpu_frame_encode_h264_tail(void)
{
    int ret = 0;
    /**** Stop/Release Encoder ***********************************************/
    ret = csi_vpu_h264enc_stop(handle_enc, &output);
	ASSERT_TRUE_WITH_RETURN(ret == 0,ret, ret);

#if output_en 	
    memcpy((void *)(output_stream_addr + stats_stream_length),output.addr, output.size);
	stats_stream_length += output.size;
#endif

    return ret;
}

int vpu_frame_encode_end(void)
{
    int ret = 0;
    /**** Release Encoder ***********************************************/
    /* Destory H264 encoder instance */
    ret = csi_vpu_destory_instance(handle_enc);
    ASSERT_TRUE_WITH_RETURN(ret == 0,ret, ret);
    HW_PRINTF("frame encode finish\r\n");

    return ret;
}


int h264_vpu_unit_test(int time)
{
	long long curtime = 0;
	int encode_count = 0;
    printf("time = %d\n", time);
	vpu_frame_result data_frame_info;
    vpu_h264_init();
	vpu_h264enc_config();
	vpu_frame_encode_start();

	while(1)
	{
		curtime = aos_now_ms();
	    if(h264_header_en == 1){
	    vpu_frame_encode_h264_header(&data_frame_info);
				//TODO:data_get
	    }
		
		vpu_frame_encode_begin();
		
		vpu_frame_encode_data(&data_frame_info);
				//TODO:data_get
		if(config.gop_len == next_pic){
			h264_header_en = 1;
			vpu_frame_encode_h264_tail();
		     encode_count++;
		}
		else{
		     h264_header_en = 0;
		}

		printf("ftime %lld\n", aos_now_ms() - curtime);
		
		aos_msleep(5);

#if output_en		
		if(config.gop_len == next_pic){
			if(encode_count == 3){
         		break;
			}
		}
#endif
	// printf(" 360*640=%d\n",encode_count);

	}
  
	
#if output_en
	printf(" dump binary memory h264.h264 %p %p length=%d\n",output_stream_addr, (output_stream_addr + stats_stream_length),stats_stream_length);
	if(NULL != output_stream_addr){
		//free(output_stream_addr);  //free
		//output_stream_addr = NULL;
	}
	stats_stream_length = 0;
#endif
 //  while(1){};
  // vpu_frame_encode_end();
//	vpu_h264_teardown();
	return 0;
}

#define VPU_TSK_EVENT_QUIT		1
typedef struct {
	aos_event_t tsk_event;
	int frame_period_ms;
	h264_frame_ringbuff_t *cache;
	int tsk_running;
	int vpu_running;
	int init_done;

	vpu_h264enc_config_t config;
	vpu_handle_t  	vpu_hdl;
	vpu_instance_t 	vpu_inst;
} vpu_tsk_t;

static vpu_tsk_t g_vpu_tsk_info;

static void vpu_tsk(void *arg)
{
	long long starttime = 0;
	int ret;
	int i;
	int frame_cnt = 0;
	vpu_picture_t input;
	vpu_es_frame_t output;
    long long last_time_stamp = aos_now_ms();
    long long cur_time_stamp = 0;
    long long time_stamp_offset = 0;  // make sure time stamp starts from zero
	uint8_t *input_raw_addrs[3] = {(uint8_t*)INPUT_IMAGE_ADDR0, (uint8_t*)INPUT_IMAGE_ADDR1, (uint8_t*)INPUT_IMAGE_ADDR2};

	h264_cache_reset(g_vpu_tsk_info.cache);
	while(g_vpu_tsk_info.tsk_running || (frame_cnt % g_vpu_tsk_info.config.gop_len != 0))
	{
		starttime = aos_now_ms();
		input.input_addr = input_raw_addrs[ispx_buf_index % 3];

		if (frame_cnt % g_vpu_tsk_info.config.gop_len == 0)
		{
			input.frame_type = VPU_FRAME_TYPE_IFRAME;
			frame_cnt = 0;
		}
		else
		{
			input.frame_type = VPU_FRAME_TYPE_PFRAME;
		}

        if (cur_time_stamp == 0) {
            /* first time, time_stamp_offset should be zero + compensation 3 ms */
            cur_time_stamp = last_time_stamp + 3;
			LOGD(TAG, "first video frame");
        } else {
            cur_time_stamp = aos_now_ms();
        }

        time_stamp_offset += cur_time_stamp - last_time_stamp;
        last_time_stamp = cur_time_stamp;

		if (input.frame_type == VPU_FRAME_TYPE_IFRAME) {
			ret = csi_vpu_h264enc_start(g_vpu_tsk_info.vpu_inst, &output);
			aos_check_return(ret == 0);
			
			h264_cache_put_async(g_vpu_tsk_info.cache, H264_TYPE_IFRAME, output.addr, output.size, time_stamp_offset);

			// LOGD(TAG, "encode i frame");
			// printf("encode i-frame\n");
		}


		ret = csi_vpu_h264enc_encode(g_vpu_tsk_info.vpu_inst, &input);
		aos_check_return(ret == 0);

		/* wait for interrupt */
		for (i = 0; i < 100 && frame_encode_ready == 0; i++) {
			aos_msleep(5);
		}
		frame_encode_ready = 0;

		if (i == 100) {
			LOGE(TAG, "encode timeout");
		}

		ret = csi_vpu_h264enc_get_stream(g_vpu_tsk_info.vpu_inst, NULL, &output);
		aos_check_return(ret == 0);

		/* put SPS and iframe in the same frame */
		h264_cache_put_async(	g_vpu_tsk_info.cache, 
						input.frame_type == VPU_FRAME_TYPE_IFRAME ? H264_TYPE_IFRAME : H264_TYPE_PFRAME, 
						output.addr, output.size, time_stamp_offset);
	
		frame_cnt++;
		// printf("ftime %d\n", aos_now_ms() - curtime);

		// add tail to stream?
		if (frame_cnt == g_vpu_tsk_info.config.gop_len) {
			ret = csi_vpu_h264enc_stop(g_vpu_tsk_info.vpu_inst, &output);
			aos_check_return(ret == 0);

			h264_cache_put_async(g_vpu_tsk_info.cache, -1, output.addr, output.size, time_stamp_offset);
		}
		h264_cache_put_done(g_vpu_tsk_info.cache);

		int time_spend = aos_now_ms() - starttime;
		if (time_spend < g_vpu_tsk_info.frame_period_ms) {
			aos_msleep(g_vpu_tsk_info.frame_period_ms - time_spend);
		}
	}

	aos_event_set(&g_vpu_tsk_info.tsk_event, VPU_TSK_EVENT_QUIT, AOS_EVENT_OR);
}

int h264_vpu_init(void)
{
	int ret;
	
	g_vpu_tsk_info.frame_period_ms = 40;		// 25 fps

	g_vpu_tsk_info.cache = h264_cache_init(25);
	aos_check_return_einval(g_vpu_tsk_info.cache);

    // vpu_h264_init();
    // ret = vpu_h264enc_init_config(&g_vpu_tsk_info.config, NULL);
	// aos_check_return_einval(ret == 0);
	// ret = vpu_frame_encode_start();
	// aos_check_return_einval(ret == 0);

	/* Encoder input parameters */
	g_vpu_tsk_info.config.input_width = 360;//360;//720;//480;
	g_vpu_tsk_info.config.input_height = 640;//640;//1280;//640;
	g_vpu_tsk_info.config.format = VPU_INPUT_FORMAT_RGB888 ;//VPU_INPUT_FORMAT_YUV420_PLANAR;//VPU_INPUT_FORMAT_RGB888;//VPU_INPUT_FORMAT_BGR888;//VPU_INPUT_FORMAT_YUYV422

	/* Encoder output parameters */
	g_vpu_tsk_info.config.output_width = g_vpu_tsk_info.config.input_width;
	g_vpu_tsk_info.config.output_height = g_vpu_tsk_info.config.input_height;
	g_vpu_tsk_info.config.level = VPU_H264ENC_LEVEL_5;
	g_vpu_tsk_info.config.framerate_numer = 25; //5 frames per second
	g_vpu_tsk_info.config.framerate_denom = 1; /* Can't be 0 */
	g_vpu_tsk_info.config.gop_len = 25;

	drv_vpu_clk_enable(ENABLE);
    g_vpu_tsk_info.vpu_hdl = csi_vpu_initialize(0, vpu_event_cb, NULL);

	/* Create H264 encoder instance */
	g_vpu_tsk_info.vpu_inst = csi_vpu_create_instance(g_vpu_tsk_info.vpu_hdl,
									 VPU_INSTANCE_TYPE_ENCODER_H264,
									 (vpu_inst_config_t)&g_vpu_tsk_info.config);
	aos_check_return_einval(g_vpu_tsk_info.vpu_inst != NULL);

	ret = aos_event_new(&g_vpu_tsk_info.tsk_event, 0);
	aos_check_return_einval(ret == 0);
	g_vpu_tsk_info.init_done = 1;

	return 0;
}

int h264_vpu_encode_start(void)
{
	aos_task_t tsk;
	int ret;
	if(g_vpu_tsk_info.init_done == 0) {
		LOGE(TAG, "vpu not init");
		return -1;
	}
	if (g_vpu_tsk_info.vpu_running) {
		LOGW(TAG, "vpu tsk already running");
		return -1;
	}

	g_vpu_tsk_info.tsk_running = 1;
	g_vpu_tsk_info.vpu_running = 1;
	aos_event_set(&g_vpu_tsk_info.tsk_event, 0, AOS_EVENT_AND);

	ret = aos_task_new_ext(&tsk, "vpu", vpu_tsk, NULL, 4096, AOS_DEFAULT_APP_PRI - 10);
	if (ret != 0) {
		g_vpu_tsk_info.tsk_running = 0;
		g_vpu_tsk_info.vpu_running = 0;
		LOGE(TAG, "vpu task start failed");
		return -1;
	}
	
	return 0;
}

int h264_vpu_encode_stop(void)
{
	if (g_vpu_tsk_info.vpu_running == 0) {
		LOGW(TAG, "vpu task not running");
		return -1;
	}

	unsigned int flags;
	g_vpu_tsk_info.tsk_running = 0;
    aos_event_get(&g_vpu_tsk_info.tsk_event, VPU_TSK_EVENT_QUIT, AOS_EVENT_OR_CLEAR, &flags, AOS_WAIT_FOREVER);
	g_vpu_tsk_info.vpu_running = 0;
	
	return 0;
}

int h264_vpu_deinit(void)
{
	return 0;
}

int h264_vpu_get_frame(h264_frame_type_e *type, uint8_t *data, int max_size, long long *time_stamp)
{
	return h264_cache_get(g_vpu_tsk_info.cache, type, data, max_size, time_stamp);
}

int h264_vpu_available_frame(void)
{
	return h264_cache_available_read(g_vpu_tsk_info.cache);
}

int h264_vpu_seek_iframe(void)
{
	return h264_cache_seek_iframe(g_vpu_tsk_info.cache);
}

// void h264_vpu_get_frame_done(void)
// {
// 	h264_cache_get_done(g_vpu_tsk_info.cache);
// }
